package com.gm.census.service.impl;

import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import com.gm.census.controller.QueueController;
import com.gm.census.controller.QueueController.Queues;
import com.gm.census.model.pojo.ContactInfo;
import com.gm.census.model.pojo.Entry;
import com.gm.census.model.pojo.Note;
import com.gm.census.model.pojo.SearchEntry;
import com.gm.census.service.BaseService;
import com.gm.census.service.EntrySearchResult;
import com.gm.census.service.EntryService;
import com.gm.census.service.PagedListResult;
import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.Email;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.google.appengine.api.search.AddException;
import com.google.appengine.api.search.Consistency;
import com.google.appengine.api.search.Document;
import com.google.appengine.api.search.Field;
import com.google.appengine.api.search.Index;
import com.google.appengine.api.search.IndexSpec;
import com.google.appengine.api.search.SearchServiceFactory;
import com.google.appengine.api.search.StatusCode;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions.Method;
import com.googlecode.objectify.AsyncObjectify;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;

public abstract class EntryServiceImpl extends BaseServiceImpl<Entry, Long>
		implements EntryService {

	static {
		ObjectifyService.register(Entry.class);
		ObjectifyService.register(Note.class);
		ObjectifyService.register(ContactInfo.class);
		ObjectifyService.register(SearchEntry.class);
	}

	private static final Logger log = Logger.getLogger(EntryServiceImpl.class
			.getName());

	protected abstract Key<String> parent();

	protected Key<String> searchParent() {
		return new Key<String>(String.class, "search");
	}

	private Key<Entry> key(Long id) {
		return new Key<Entry>(parent(), Entry.class, id);
	}

	private Query<Entry> query(String index, Integer pageSize) {
		Query<Entry> q = begin().query(Entry.class).ancestor(parent());
		if (pageSize > 0)
			q.limit(pageSize);
		else
			q.limit(BaseService.DEFAULT_QUERY_PAGE);

		if (index != null && index.length() > 0) {
			Cursor cursor = Cursor.fromWebSafeString(index);
			q.startCursor(cursor);
		}

		return q;
	}

	@Override
	public Entry find(final Long id) {
		return inNs(new DefaultNSCmd<Entry>() {

			@Override
			public Entry execute() {
				return begin().find(key(id));
			}

		});
	}

	private Index getIndex() {
		IndexSpec spec = IndexSpec.newBuilder().setName("entries")
				.setConsistency(Consistency.GLOBAL).build();
		return SearchServiceFactory.getSearchService().getIndex(spec);
	}

	public void addDocument(Entry e) {

		Document doc = Document
				.newBuilder()
				.setId("document_" + e.getId())
				.addField(
						Field.newBuilder().setName("timestamp")
								.setDate(new Date()))
				.addField(
						Field.newBuilder().setName("name").setText(e.getName()))
				.addField(
						Field.newBuilder().setName("country")
								.setText(e.getCountry()))
				.addField(
						Field.newBuilder().setName("street")
								.setText(e.getStreet()))
				.addField(
						Field.newBuilder().setName("district")
								.setText(e.getDistrict()))
				.addField(
						Field.newBuilder().setName("city").setText(e.getCity()))
				.addField(
						Field.newBuilder()
								.setName("fulltext")
								.setText(
										e.getName() + ";" + e.getCountry()
												+ ";" + e.getStreet() + ";"
												+ e.getDistrict() + ";"
												+ e.getCity())).build();
		try {
			getIndex().add(doc);
		} catch (AddException exc) {
			if (StatusCode.TRANSIENT_ERROR.equals(exc.getOperationResult()
					.getCode())) {
				getIndex().add(doc);
			}
		}
	}

	protected void enqueueAddEntry(Entry e) {
		Queues entry = QueueController.Queues.entry;
		Queue q = QueueFactory.getQueue(entry.queueName());
		q.add(withUrl(entry.url(String.valueOf(e.getId()))).method(Method.PUT)
				.param("name", e.getName()).param("country", e.getCountry())
				.param("street", e.getStreet())
				.param("district", e.getDistrict()).param("city", e.getCity())
				.param("homephone", e.getHomephone())
				.param("cellphone", e.getCellphone())
				.param("workphone", e.getWorkphone())
				.header("usr-ns", NamespaceManager.getGoogleAppsNamespace())
				.header("Accept", "application/json"));

	}

	protected void enqueueRemoveEntry(Entry e) {
		Queue q = QueueFactory.getDefaultQueue();
		q.add(withUrl(
				QueueController.Queues.entry.url(String.valueOf(e.getId())))
				.method(Method.DELETE).header("Content-Type",
						"application/json"));
	}

	private Collection<String> loadSplit(String val, Collection<String> list) {
		if (val == null)
			return list;
		if (list == null)
			list = new ArrayList<String>();
		for (String s : val.split("[\\s_-]")) {
			list.add(s.toUpperCase().trim());
		}
		return list;
	}

	@Override
	public Entry put(final Entry e) {
		return inNs(new DefaultNSCmd<Entry>() {

			@Override
			public Entry execute() {
				e.setParent(parent());
				List<Email> emails = new ArrayList<Email>();
				for (String email : e.getEmails()) {
					emails.add(new Email(email));
				}
				Objectify ofy = begin();
				Key<Entry> k = ofy.put(e);

				AsyncObjectify async = ofy.async();

				ContactInfo cf = new ContactInfo(
						emails.toArray(new Email[] {}), e.getWorkphone(),
						e.getHomephone(), e.getCellphone(), k);
				async.put(cf);
				List<Note> notes = new ArrayList<Note>();
				for (String note : e.getNotes()) {
					notes.add(new Note(new Date(), getUserInfo()
							.getLoggedUserId(), note, k));
				}

				async.put(notes);

				SearchEntry se = new SearchEntry(k);
				Set<String> values = new HashSet<String>();

				loadSplit(e.getName(), values);
				loadSplit(e.getStreet(), values);
				loadSplit(e.getDistrict(), values);
				loadSplit(e.getCity(), values);
				loadSplit(e.getCountry(), values);
				values.add(e.getHomephone());
				values.add(e.getWorkphone());
				values.add(e.getCellphone());
				se.setValues(values.toArray(new String[values.size()]));
				async.put(se);

				e.setId(k.getId());
				return e;
			}

		});
	}

	@Override
	public void delete(final Long id) {
		inNs(new DefaultNSCmd<Object>() {
			@Override
			public Object execute() {
				Objectify ofy = begin();
				ofy.delete(key(id));
				List<Note> notes = getNotes(id);
				ofy.delete(notes);
				ContactInfo cf = getContactInfo(id);
				ofy.delete(cf);
				return null;
			}

		});

	}

	@Override
	public Entry get(final Long id) {
		return inNs(new DefaultNSCmd<Entry>() {
			@Override
			public Entry execute() {
				Objectify ofy = begin();
				Entry e = ofy.get(key(id));
				return e;
			}
		});
	}

	@Override
	public List<Note> getNotes(final Long id) {
		return inNs(new DefaultNSCmd<List<Note>>() {

			@Override
			public List<Note> execute() {
				return begin().query(Note.class).ancestor(key(id)).list();
			}

		});
	}

	@Override
	public ContactInfo getContactInfo(final Long id) {
		return inNs(new DefaultNSCmd<ContactInfo>() {
			@Override
			public ContactInfo execute() {
				List<ContactInfo> cf = begin().query(ContactInfo.class)
						.ancestor(key(id)).list();
				if (cf != null && cf.size() > 0)
					return cf.get(0);
				return null;
			}
		});
	}

	@Override
	public List<Entry> list() {
		return list(null, 0).getReturn();
	}

	@Override
	public PagedListResult<List<Entry>> list(final String index,
			final Integer pageSize) {
		return inNs(new DefaultNSCmd<PagedListResult<List<Entry>>>() {

			@Override
			public PagedListResult<List<Entry>> execute() {
				return new GAEPagedListResult<Entry>(query(index, pageSize)
						.iterator());
			}

		});
	}

	@Override
	public PagedListResult<List<Entry>> list(String index) {
		return list(index, 0);
	}

	@Override
	public EntrySearchResult<Entry> search(final String text,
			final String index, final Integer pageSize) {
		log.info("search");
		EntrySearchResult<Entry> ret = inNs(new DefaultNSCmd<EntrySearchResult<Entry>>() {

			@Override
			public EntrySearchResult<Entry> execute() {
				log.info("execute");
				Query<SearchEntry> q = begin().query(SearchEntry.class);
				if (index != null && index.length() > 0) {
					q.startCursor(Cursor.fromWebSafeString(index));
				}
				if (pageSize != null && pageSize > 0) {
					q.limit(pageSize);
				} else {
					q.limit(BaseService.DEFAULT_QUERY_PAGE);
				}

				for (String s : text.split(",")) {
					q.filter("values =", s.toUpperCase().trim());
				}

				QueryResultIterator<SearchEntry> it = q.iterator();

				List<Key<Entry>> entries = new ArrayList<Key<Entry>>();
				while (it.hasNext()) {
					entries.add(it.next().getEntry());
				}

				EntrySearchResultImpl res = new EntrySearchResultImpl();

				res.setNextPageIndex(it.getCursor().toWebSafeString());
				List<Entry> ind = null, vis = null;
				for (Entry e : begin().get(entries).values()) {
					if (e.getConfirmed()) {
						if (vis == null)
							vis = new ArrayList<Entry>();
						vis.add(e);
					} else {
						if (ind == null)
							ind = new ArrayList<Entry>();
						ind.add(e);
					}
				}
				res.setIndications(ind);
				res.setVisits(vis);
				log.info("exit execute");
				return res;
			}

		});
		log.info("exit search");
		return ret;
	}


}
